﻿// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

namespace Neto.Logging;

/// <summary>
///     日志工厂类，用于创建和管理不同级别的日志记录器。
/// </summary>
public class LogXFactory
{
    /// <summary>
    ///     初始化日志工厂实例。
    /// </summary>
    /// <param name="folderName">日志文件夹名称，默认为 "all"。</param>
    /// <param name="categoryName">日志分类名称，默认为 "default"。</param>
    /// <param name="maxRollingFiles">最大滚动文件数，默认为 720（30天，每天24个文件）。</param>
    /// <param name="fileSizeLimitBytes">单个日志文件的最大大小，默认为 100MB。</param>
    /// <param name="useUtcTimestamp">是否使用UTC时间戳，默认为 false。</param>
    /// <param name="dateType">日志文件的日期类型，默认为按天分割。</param>
    public LogXFactory(string folderName = "all",
        string categoryName = "default",
        int maxRollingFiles = 24 * 30,
        long fileSizeLimitBytes = 100 * 1024 * 1024,
        bool useUtcTimestamp = false,
        LogXFileDateType dateType = LogXFileDateType.Day)
    {
        FolderName = folderName;
        CategoryName = categoryName;
        MaxRollingFiles = maxRollingFiles;
        FileSizeLimitBytes = fileSizeLimitBytes;
        UseUtcTimestamp = useUtcTimestamp;
        DateType = dateType;
    }
    
    /// <summary>
    ///     存储不同日志级别的日志记录器。
    /// </summary>
    private readonly ConcurrentDictionary<LogLevel, LogXLevelItem<ILogger>> _loggers = new();

    /// <summary>
    ///     获取或设置日志文件的日期类型。
    /// </summary>
    public LogXFileDateType DateType { get; set; }

    /// <summary>
    ///     获取或设置日志文件夹名称。
    /// </summary>
    public string FolderName { get; set; }

    /// <summary>
    ///     获取或设置日志分类名称。
    /// </summary>
    public string CategoryName { get; set; }

    /// <summary>
    ///     获取或设置最大滚动文件数。
    /// </summary>
    public int MaxRollingFiles { get; set; }

    /// <summary>
    ///     获取或设置单个日志文件的最大大小（字节）。
    /// </summary>
    public long FileSizeLimitBytes { get; set; }

    /// <summary>
    ///     获取或设置是否使用UTC时间戳。
    /// </summary>
    public bool UseUtcTimestamp { get; set; }


    /// <summary>
    ///     记录跟踪级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Trace(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Trace, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录跟踪级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Trace(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Trace, null, ex, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录调试级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Debug(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Debug, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录调试级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Debug(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Debug, null, ex, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录信息级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Information(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Information, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录信息级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Information(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Information, null, ex, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录警告级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Warning(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Warning, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录警告级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Warning(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Warning, null, ex, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录错误级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Error(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Error, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录错误级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Error(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Error, null, ex, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录致命级别的日志。
    /// </summary>
    /// <param name="content">日志内容。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Critical(string content, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Critical, content, null, logFolderName, isWebInfoLog);
    }

    /// <summary>
    ///     记录致命级别的异常日志。
    /// </summary>
    /// <param name="ex">异常对象。</param>
    /// <param name="logFolderName">日志文件夹名称。</param>
    /// <param name="isWebInfoLog">是否记录Web信息。</param>
    public void Critical(Exception ex, string logFolderName = "", bool isWebInfoLog = false)
    {
        LogInternal(LogLevel.Critical, null, ex, logFolderName, isWebInfoLog);
    }


    // /// <summary>
    // ///     记录异常日志。
    // /// </summary>
    // /// <param name="exception">异常对象</param>
    // /// <param name="logFolderName">文件夹：例如：错误\致命错误</param>
    // /// <param name="logLevel">日志等级</param>
    // /// <param name="isWebInfoLog">是否记录web信息</param>
    // public void Log(Exception exception, string logFolderName = "", LogLevel logLevel = LogLevel.Debug,
    //     bool isWebInfoLog = false)
    // {
    //     Log(null, exception, logFolderName, logLevel, isWebInfoLog);
    // }

    /// <summary>
    ///     记录日志。
    /// </summary>
    /// <param name="level">日志等级</param>
    /// <param name="content">日志内容</param>
    /// <param name="ex">异常对象</param>
    /// <param name="logFolderName">文件夹：例如：错误\致命错误</param>
    /// <param name="isWebInfoLog">是否记录web信息</param>
    private void LogInternal(LogLevel level, string? content = null, Exception? ex = null,
        string logFolderName = "", bool isWebInfoLog = false)
    {
        Log(content, ex, logFolderName, level, isWebInfoLog);
    }

    /// <summary>
    ///     记录异常日志。
    /// </summary>
    /// <param name="msg">日志内容</param>
    /// <param name="exception">异常对象</param>
    /// <param name="logFolderName">文件夹：例如：错误\致命错误</param>
    /// <param name="logLevel">日志等级</param>
    /// <param name="isWebInfoLog">是否记录web信息</param>
    public void Log(string? msg = "", Exception? exception = null, string logFolderName = "",
        LogLevel logLevel = LogLevel.Debug, bool isWebInfoLog = false)
    {
            // 如果日志消息为空且没有异常，则不记录日志。
            if (string.IsNullOrWhiteSpace(msg) && exception == null) return;
            
            var sb = new StringBuilder();
            if (isWebInfoLog) AppX.GetWebInfoToString(sb);
            sb.AppendLine(msg ?? string.Empty);
            if (exception != null) sb.AppendLine($"异常日志：{exception.ToStringEx()}");

            var logger = GetLoggerItem(logLevel);
            if (logger.Logger == null)
            {
                logger.Logger = logger.LoggerFactory.CreateLogger(logger.LoggerFactory.GetType().Name);
            }
            switch (logLevel)
            {
                case LogLevel.Trace: //冗余的
                    logger.Logger?.LogTrace(sb.ToString());
                    break;

                case LogLevel.Debug: //调试
                    logger.Logger?.LogDebug(sb.ToString());
                    break;

                case LogLevel.Information: //信息
                    logger.Logger?.LogInformation(sb.ToString());
                    break;

                case LogLevel.Warning: //警告
                    logger.Logger?.LogWarning(sb.ToString());
                    break;

                case LogLevel.Error: //错误
                    logger.Logger?.LogError(sb.ToString());
                    break;

                case LogLevel.Critical: //致命
                    logger.Logger?.LogCritical(sb.ToString());
                    break;
                case LogLevel.None: //
                    break;
                default:
                    break;
            }
    }

    /// <summary>
    ///     获取指定日志级别的日志记录器。
    /// </summary>
    /// <param name="logLevel">日志级别。</param>
    /// <returns>日志记录器。</returns>
    private ILogger GetLogger(LogLevel logLevel = LogLevel.Debug)
    {
        var logXLevelItem = GetLoggerItem(logLevel);
        return logXLevelItem.Logger;
    }

    /// <summary>
    ///     获取或创建指定日志级别的日志记录器项。
    /// </summary>
    /// <param name="logLevel">日志级别。</param>
    /// <returns>日志记录器项。</returns>
    private LogXLevelItem<ILogger> GetLoggerItem(LogLevel logLevel = LogLevel.Debug)
    {
        var dateTimeNow = DateTimeOffset.Now;
        if (UseUtcTimestamp)
            dateTimeNow = DateTimeOffset.UtcNow;
        //定义变量，标识是否需要创建新的Logger对象
        var isNewLogger = false;
        //_loggers.Loggers 中不存在miLevel 则新增，否则返回
        if (!_loggers.TryGetValue(logLevel, out var logXLevelItem))
        {
            logXLevelItem = new LogXLevelItem<ILogger>(logLevel);
            isNewLogger = true;
        }
        else
        {
            // 检查日志记录器项的日期是否与当前日期匹配
            if (DateType == LogXFileDateType.Year && logXLevelItem.Date == $"{dateTimeNow:yyyy}") return logXLevelItem;

            if (DateType == LogXFileDateType.Month && logXLevelItem.Date == $"{dateTimeNow:yyyyMM}")
                return logXLevelItem;

            if (DateType == LogXFileDateType.Day && logXLevelItem.Date == $"{dateTimeNow:yyyyMMdd}")
                return logXLevelItem;

            if (DateType == LogXFileDateType.Hours && logXLevelItem.Date == $"{dateTimeNow:yyyyMMddHH}")
                return logXLevelItem;

            //移除旧的Logger对象
            logXLevelItem.LoggerFactory?.Dispose();
            _loggers.TryRemove(logLevel, out _);
            logXLevelItem = new LogXLevelItem<ILogger>(logLevel);
        }

        if (isNewLogger)
        {
            var fileNameTemplet = FileNameTemplates[DateType];
            logXLevelItem.Date = DateType switch
            {
                LogXFileDateType.Year => $"{dateTimeNow:yyyy}",
                LogXFileDateType.Month => $"{dateTimeNow:yyyyMM}",
                LogXFileDateType.Day => $"{dateTimeNow:yyyyMMdd}",
                LogXFileDateType.Hours => $"{dateTimeNow:yyyyMMddHH}",
                _ =>  throw new ArgumentOutOfRangeException(nameof(DateType), DateType, "不支持的日志文件日期类型")
            };

            var loggerFactory = CreateLoggerFactory(logLevel, fileNameTemplet);

            logXLevelItem.Logger = loggerFactory.CreateLogger(CategoryName);
            logXLevelItem.LoggerFactory = loggerFactory;

            _loggers.TryAdd(logLevel, logXLevelItem);
        }

        return logXLevelItem;
    }
    /// <summary>
    ///     创建日志记录器工厂。
    /// </summary>
    /// <param name="level"></param>
    /// <param name="dateTemplate"></param>
    /// <returns></returns>
    private ILoggerFactory CreateLoggerFactory(LogLevel level, string dateTemplate)
    {
        return LoggerFactory.Create(builder =>
        {
            builder.SetMinimumLevel(level);
            builder.AddFile(dateTemplate, options =>
            {
                options.FileNameRule = fileName => string.Format(fileName, DateTime.UtcNow, level.ToString(), FolderName.ToOsDirectory());
                //options.WriteFilter = logMsg => logMsg.LogLevel == logLevel;
                options.MaxRollingFiles = MaxRollingFiles;
                options.FileSizeLimitBytes = FileSizeLimitBytes;
                options.UseUtcTimestamp = UseUtcTimestamp;//是否使用 UTC 时间戳，默认 false
            });
        });
    }
    
    /// <summary>
    ///     文件名模板
    /// </summary>
    private static readonly Dictionary<LogXFileDateType, string> FileNameTemplates = new()
    {
        { LogXFileDateType.Year, Path.Combine("logs", "{2}", "{1}-{0:yyyy}.log").ToOsDirectory() },
        { LogXFileDateType.Month, Path.Combine("logs", "{2}", "{1}-{0:yyyy}-{0:MM}.log").ToOsDirectory() },
        { LogXFileDateType.Day, Path.Combine("logs", "{2}", "{1}-{0:yyyy}-{0:MM}-{0:dd}.log").ToOsDirectory() },
        { LogXFileDateType.Hours, Path.Combine("logs", "{2}", "{1}-{0:yyyy}-{0:MM}-{0:dd}-{0:HH}.log").ToOsDirectory() }
    };
    
    private bool _disposed = false;

    /// <summary>
    ///     释放资源
    /// </summary>
    public void Dispose()
    {
        if (_disposed) return;

        foreach (var item in _loggers.Values)
        {
            item.LoggerFactory?.Dispose();
        }
        _loggers.Clear();
        _disposed = true;
    }
}